package com.simple.handler.impl;

import com.simple.anno.AfterLogic;
import com.simple.anno.AgentClass;
import com.simple.anno.BeforeLogic;
import com.simple.handler.AbstractHandler;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

public class NameMethodHandler extends AbstractHandler {

    protected Object proxy;

    protected List<AgentInfo> beforeInfo, afterInfo;

    public static class AgentInfo {

        private Method method;

        private List<String> methodNames;

        private boolean isUseParam;
    }

    public NameMethodHandler() {
        this.proxy = this;
        beforeInfo = new ArrayList<>();
        afterInfo = new ArrayList<>();
    }

    @Deprecated
    @Override
    public void before(Object... objects) throws InvocationTargetException, IllegalAccessException {

    }

    @Deprecated
    @Override
    public void after(Object... objects) throws InvocationTargetException, IllegalAccessException {

    }

    /**
     * 设置代理类对象
     *
     * @param proxy 代理类对象
     */
    public void setProxy(Object proxy) {
        this.proxy = proxy;
        Class<?> aClass = proxy.getClass();
        AgentClass agentClass = aClass.getDeclaredAnnotation(AgentClass.class);
        init(agentClass.supperClass()[0]);
    }

    @Override
    public void init(Class<?> aClass) {
        enhancer = new Enhancer();
        enhancer.setSuperclass(aClass);
        enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
            AgentInfo agentInfo = isExist(beforeInfo, method);
            if (agentInfo != null) {
                if (agentInfo.isUseParam) {
                    beforeMethod(agentInfo.method, args);
                } else {
                    beforeMethod(agentInfo.method);
                }
            }
            Object object = proxy.invokeSuper(obj, args);
            agentInfo = isExist(afterInfo, method);
            if (agentInfo != null) {
                if (agentInfo.isUseParam) {
                    afterMethod(agentInfo.method, args);
                } else {
                    afterMethod(agentInfo.method);
                }
            }
            return object;
        });
        Method[] methods = proxy.getClass().getDeclaredMethods();
        for (Method method : methods) {
            BeforeLogic beforeLogic = method.getDeclaredAnnotation(BeforeLogic.class);
            if (beforeLogic != null) {
                String[] proxyMethod = beforeLogic.proxyMethod();
                AgentInfo agentInfo = new AgentInfo();
                agentInfo.isUseParam = beforeLogic.isUseParam();
                agentInfo.methodNames = new ArrayList<>(Arrays.asList(proxyMethod));
                agentInfo.method = method;
                beforeInfo.add(agentInfo);
                continue;
            }
            AfterLogic afterLogic = method.getDeclaredAnnotation(AfterLogic.class);
            if (afterLogic != null) {
                String[] proxyMethod = afterLogic.proxyMethod();
                AgentInfo agentInfo = new AgentInfo();
                agentInfo.isUseParam = afterLogic.isUseParam();
                agentInfo.methodNames = new ArrayList<>(Arrays.asList(proxyMethod));
                agentInfo.method = method;
                afterInfo.add(agentInfo);
            }
        }
    }

    private void beforeMethod(Method method, Object... objects) throws InvocationTargetException, IllegalAccessException {
        method.setAccessible(true);
        if (objects == null) {
            method.invoke(proxy);
        } else {
            method.invoke(proxy, objects);
        }
    }

    private void afterMethod(Method method, Object... objects) throws InvocationTargetException, IllegalAccessException {
        method.setAccessible(true);
        if (objects == null) {
            method.invoke(proxy);
        } else {
            method.invoke(proxy, objects);
        }
    }

    private AgentInfo isExist(List<AgentInfo> list, Method method) {
        for (AgentInfo agentInfo : list) {
            List<String> methodNames = agentInfo.methodNames;
            if (methodNames.contains(method.toString())) {
                return agentInfo;
            }
        }
        return null;
    }
}
